<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
               "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<title>Yagra</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<meta name="title" content="Yagra"/>
<meta name="generator" content="Org-mode"/>
<meta name="generated" content="2013-03-14 19:28 Thursday"/>
<meta name="author" content="Hua Liang [Stupid ET]"/>
<meta name="description" content=""/>
<meta name="keywords" content=""/>

<link rel="stylesheet" type="text/css" href="default.css" />


</head>
<body>

<div id="preamble">

</div>

<div id="content">
<h1 class="title">Yagra</h1>


<div id="table-of-contents">
<h2>Table of Contents</h2>
<div id="text-table-of-contents">
<ul>
<li><a href="#sec-1">1 源码获取</a></li>
<li><a href="#sec-2">2 环境搭建</a>
<ul>
<li><a href="#sec-2-1">2.1 rewrite</a></li>
<li><a href="#sec-2-2">2.2 虚拟主机配置</a>
<ul>
<li><a href="#sec-2-2-1">2.2.1 Apache配置</a></li>
<li><a href="#sec-2-2-2">2.2.2 Nginx配置</a></li>
</ul>
</li>
<li><a href="#sec-2-3">2.3 数据库创建</a></li>
<li><a href="#sec-2-4">2.4 上传图片文件夹权限设置</a></li>
</ul>
</li>
<li><a href="#sec-3">3 yagra功能介绍</a>
<ul>
<li><a href="#sec-3-1">3.1 注册</a></li>
<li><a href="#sec-3-2">3.2 用户头像管理页面</a></li>
<li><a href="#sec-3-3">3.3 头像api</a></li>
</ul>
</li>
<li><a href="#sec-4">4 yagra的设计</a>
<ul>
<li><a href="#sec-4-1">4.1 url处理</a></li>
<li><a href="#sec-4-2">4.2 html生成</a></li>
<li><a href="#sec-4-3">4.3 选择头像</a></li>
<li><a href="#sec-4-4">4.4 数据库设计</a></li>
</ul>
</li>
</ul>
</div>
</div>

<div id="outline-container-1" class="outline-2">
<h2 id="sec-1"><span class="section-number-2">1</span> 源码获取</h2>
<div class="outline-text-2" id="text-1">

<p>  源码使用了Google Code提供的SVN服务。
</p>
<p>
  访问 <a href="https://code.google.com/p/cedricporter-yagra/source/checkout">https://code.google.com/p/cedricporter-yagra/source/checkout</a>
</p>



<pre class="src src-sh">svn checkout http://cedricporter-yagra.googlecode.com/svn/trunk/ cedricporter-yagra-read-only
</pre>



</div>

</div>

<div id="outline-container-2" class="outline-2">
<h2 id="sec-2"><span class="section-number-2">2</span> 环境搭建</h2>
<div class="outline-text-2" id="text-2">

<p>  因为要求Apache，我们就选用Apache，而且Apache对于CGI的支持也是不错的。
</p>

</div>

<div id="outline-container-2-1" class="outline-3">
<h3 id="sec-2-1"><span class="section-number-3">2.1</span> rewrite</h3>
<div class="outline-text-3" id="text-2-1">

<p>  对于Apache，我们首先需要打开它的rewrite模块。因为需要Apache将所有的请求全部rewrite到指定处理器后面。
</p>



<pre class="src src-sh">a2enmode rewrite
service apache2 restart
</pre>


</div>

</div>

<div id="outline-container-2-2" class="outline-3">
<h3 id="sec-2-2"><span class="section-number-3">2.2</span> 虚拟主机配置</h3>
<div class="outline-text-3" id="text-2-2">


</div>

<div id="outline-container-2-2-1" class="outline-4">
<h4 id="sec-2-2-1"><span class="section-number-4">2.2.1</span> Apache配置</h4>
<div class="outline-text-4" id="text-2-2-1">




<pre class="example">&lt;VirtualHost *:81&gt;
    ServerName yagra.everet.org
    ServerAdmin et@everet.org

    DocumentRoot /var/www/yagra/
    ErrorLog /var/log/apache2/yagra_errors.log
    CustomLog /var/log/apache2/yagra_accesses.log common

    AddHandler cgi-script .py
    DirectoryIndex main.py

    &lt;Directory /var/www/yagra&gt;
        AllowOverride None
        Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
        Order allow,deny
        Allow from all
        &lt;IfModule mod_rewrite.c&gt;
            RewriteEngine on
            RewriteBase /
            RewriteCond %{REQUEST_FILENAME} !-f
            RewriteCond %{REQUEST_FILENAME} !-d
            RewriteRule ^(.*)$ main.py/$1 [L]
        &lt;/IfModule&gt;
    &lt;/Directory&gt;
&lt;/VirtualHost&gt;
</pre>

<p>
   我们将Apache的监听端口设为81,是因为我的服务器的前端Web Server为Nginx，监听80。所以通过Nginx将80端口的请求转发到Apache。
</p>
</div>

</div>

<div id="outline-container-2-2-2" class="outline-4">
<h4 id="sec-2-2-2"><span class="section-number-4">2.2.2</span> Nginx配置</h4>
<div class="outline-text-4" id="text-2-2-2">




<pre class="example">server {
    server_name yagra.everet.org;

    client_max_body_size 2M;

    location / {
        proxy_pass http://127.0.0.1:81;
    }
}
</pre>


<p>
    对于Nginx的配置仅需要转发就好了。
</p>
</div>
</div>

</div>

<div id="outline-container-2-3" class="outline-3">
<h3 id="sec-2-3"><span class="section-number-3">2.3</span> 数据库创建</h3>
<div class="outline-text-3" id="text-2-3">

<p>   我在scheme.sql里面编写好了创建数据库，创建表以及新建用户授予权限的sql了。
</p>
<p>
   所以需要一个有权限的用户来运行scheme.sql来为yagra创建数据库。
</p>



<pre class="src src-sh">mysql -u root -p &lt; scheme.sql
</pre>


</div>

</div>

<div id="outline-container-2-4" class="outline-3">
<h3 id="sec-2-4"><span class="section-number-3">2.4</span> 上传图片文件夹权限设置</h3>
<div class="outline-text-3" id="text-2-4">

<p>   请在当前站点根目录创建一个文件夹*uploads*，并且授予CGI脚本运行用户对uploads文件夹有可读可写的权限。
</p>

</div>
</div>

</div>

<div id="outline-container-3" class="outline-2">
<h2 id="sec-3"><span class="section-number-2">3</span> yagra功能介绍</h2>
<div class="outline-text-2" id="text-3">



</div>

<div id="outline-container-3-1" class="outline-3">
<h3 id="sec-3-1"><span class="section-number-3">3.1</span> 注册</h3>
<div class="outline-text-3" id="text-3-1">

<p>   url: <a href="file:///accounts/signup">/accounts/signup</a>
</p>
<p>
   对于yagra的注册，需要用户名、邮箱以及密码。用户名和邮箱都不能与之前用户重复。在选择用户名的时候，可以点击检查来检查用户名是否合法。
</p>
<p>
   除此之外，在点击注册按钮的时候，会向服务器检查所有的输入是否合法，只有都合法的时候才会提交表单，以免页面跳转导致用户需要重新输入。
</p>
<p>
   <img src="./signup.png"  alt="./signup.png" />
</p>
</div>

</div>

<div id="outline-container-3-2" class="outline-3">
<h3 id="sec-3-2"><span class="section-number-3">3.2</span> 用户头像管理页面</h3>
<div class="outline-text-3" id="text-3-2">

<p>   url: <a href="file:///user">/user</a>
</p>
<p>
   用户可以上传多张头像，然后可以通过点击头像，切换用户需要的头像。
</p>
<p>
   <img src="./userhome.png"  alt="./userhome.png" />
</p>
</div>

</div>

<div id="outline-container-3-3" class="outline-3">
<h3 id="sec-3-3"><span class="section-number-3">3.3</span> 头像api</h3>
<div class="outline-text-3" id="text-3-3">

<p>   url: <a href="http://yagra.everet.org/avatar/6c50163a2c7165a5725cbf01bf04a68b">/avatar/{email's md5}</a>
</p>
<p>
   头像访问api通过email的md5计算出来的url进行获取。
</p>



<pre class="src src-python"><span style="color: #dcdccc; background-color: #3f3f3f;">avatar_url</span> = <span style="color: #cc9393;">"http://yagra.everet.org/avatar/"</span> + hashlib.md5(email.lower())
</pre>


<p>
   对于头像api的访问，有进行缓存优化。
</p>
<p>
   除了基本的设置300秒的缓存，让浏览器5分钟内不要再请求服务器外，还有利用ETag防止不必要的数据传输。
</p>



<pre class="example">Cache-Control: max-age=300
</pre>


<p>
   ETag的计算是直接使用图像文件名的sha1，因为在保存图像的时候，就确保每一幅图像拥有一个唯一的文件名，而且一张已经上传了图片不会更新。
</p>
<p>
   因此这样通过唯一的文件名计算ETag的速度会比根据整张图像内容计算要快得多。
</p>

</div>
</div>

</div>

<div id="outline-container-4" class="outline-2">
<h2 id="sec-4"><span class="section-number-2">4</span> yagra的设计</h2>
<div class="outline-text-2" id="text-4">

<p>  由于作业要求，yagra仅仅使用了Python标准库以及MySQLdb这个附加库。
</p>
<p>
  yagra的运行方式为CGI。
</p>

</div>

<div id="outline-container-4-1" class="outline-3">
<h3 id="sec-4-1"><span class="section-number-3">4.1</span> url处理</h3>
<div class="outline-text-3" id="text-4-1">

<p>   yagra的对于url处理的策略是url rewrite到一个处理文件main.py，这样可以保证url的美观。而且可以通过分配器将不同url通过正则表达式分派给不同的请求处理器。这样的代码结构会更加易于理解和维护。
</p>

</div>

</div>

<div id="outline-container-4-2" class="outline-3">
<h3 id="sec-4-2"><span class="section-number-3">4.2</span> html生成</h3>
<div class="outline-text-3" id="text-4-2">

<p>   因为不能第三方库，也就不能使用模板引擎，如果直接通过字符串拼接生成html，这样是十分的低效。
</p>
<p>
   于是我借鉴了lisp中html生成的方式，写了一个微型的模板引擎，这样我就可以方便地写html了。
</p>



<pre class="src src-python"><span style="color: #f0dfaf; font-weight: bold;">def</span> <span style="color: #8cd0d3;">homepage</span>(button_name, button_url):
    <span style="color: #dcdccc; background-color: #3f3f3f;">body_html</span> = flatten((h2(<span style="color: #cc9393;">"&#27426;&#36814;&#26469;&#21040;Yagra"</span>),
                         p(<span style="color: #cc9393;">"Yagra&#26159;&#19968;&#20010;&#20840;&#29699;&#22836;&#20687;&#31995;&#32479;&#65292;&#21487;&#20197;&#20026;&#24744;&#30340;&#37038;&#31665;&#32465;&#23450;&#19968;&#20010;&#22836;&#20687;&#12290;"</span>),
                         p(<span style="color: #cc9393;">"&#22914;&#26524;&#24744;&#36824;&#27809;&#26377;Yagra&#36134;&#21495;&#65292;&#37027;&#23601;&#36214;&#32039;&#27880;&#20876;&#19968;&#20010;&#21543;&#65281;"</span>,
                           strong(<span style="color: #dcdccc; background-color: #3f3f3f;">a</span>(k(href=<span style="color: #cc9393;">"/accounts/signup"</span>), <span style="color: #cc9393;">"&#28857;&#20987;&#27880;&#20876;"</span>))),
                         h3(<span style="color: #cc9393;">"&#22836;&#20687;&#35775;&#38382;API"</span>),
                         p(<span style="color: #cc9393;">"&#20320;&#21482;&#38656;&#35201;&#22312;yagra&#27880;&#20876;&#19968;&#20010;&#36134;&#21495;&#65292;&#23601;&#21487;&#20197;&#36890;&#36807;&#37038;&#31665;md5&#35775;&#38382;&#20320;&#30340;&#22836;&#20687;"</span>),
                         code(<span style="color: #cc9393;">'avatar_url = '</span>
                              <span style="color: #cc9393;">'"http://yagra.everet.org/avatar/" '</span>
                              <span style="color: #cc9393;">'+ hashlib.md5(email.lower())'</span>),
                         h3(<span style="color: #cc9393;">"&#20010;&#20154;&#20027;&#39029;"</span>),
                         p(<span style="color: #cc9393;">"&#38500;&#27492;&#20043;&#22806;&#65292;&#24744;&#36824;&#23558;&#25317;&#26377;&#19968;&#20010;&#20010;&#20154;&#20027;&#39029;&#21734;&#65281;&#22320;&#22336;&#20026;"</span>),
                         code(<span style="color: #cc9393;">"http://yagra.everet.org/username"</span>),
                         p(),
                         p(<span style="color: #cc9393;">"&#22914;&#25105;&#20204;"</span>, <span style="color: #dcdccc; background-color: #3f3f3f;">a</span>(k(href=<span style="color: #cc9393;">"/rose"</span>), <span style="color: #cc9393;">"rose&#30340;&#20010;&#20154;&#20027;&#39029;"</span>)),
                         ))
    <span style="color: #dcdccc; background-color: #3f3f3f;">html_string</span> = Template.basic_frame(body_html,
                                       button_name=button_name,
                                       button_url=button_url)
    <span style="color: #f0dfaf; font-weight: bold;">return</span> html_string
</pre>


<p>
   上面是我的首页的html的代码生成，直接通过html标签的同名函数来编写。而这些繁琐的函数是怎么生成的呢？
</p>
<p>
   这里我使用了Python的黑魔法exec。于是整个模板引擎的代码如下所示，非常的短就创建了许多函数。详见"everet/template.py"
</p>



<pre class="src src-python"><span style="color: #dcdccc; background-color: #3f3f3f;">t</span> = Template(<span style="color: #cc9393;">"""</span>
<span style="color: #cc9393;">def $tag(kwargs=dict(), *args):</span>

<span style="color: #cc9393;">    prefix = "&lt;$tag "</span>
<span style="color: #cc9393;">    if isinstance(kwargs, dict):</span>
<span style="color: #cc9393;">        for k, v in kwargs.iteritems():</span>
<span style="color: #cc9393;">            prefix += '%s="%s" ' % (utf8(k), utf8(str(v)))</span>
<span style="color: #cc9393;">    else:</span>
<span style="color: #cc9393;">        args = (kwargs, ) + args</span>
<span style="color: #cc9393;">    prefix += "&gt;"</span>

<span style="color: #cc9393;">    string = prefix + utf8_join_flatten(args) + "&lt;/$tag&gt;"</span>

<span style="color: #cc9393;">    return string</span>
<span style="color: #cc9393;">"""</span>)

<span style="color: #f0dfaf; font-weight: bold;">for</span> tag <span style="color: #f0dfaf; font-weight: bold;">in</span> [<span style="color: #cc9393;">"html"</span>, <span style="color: #cc9393;">"head"</span>, <span style="color: #cc9393;">"body"</span>, <span style="color: #cc9393;">"title"</span>,
            <span style="color: #cc9393;">"script"</span>, <span style="color: #cc9393;">"form"</span>, <span style="color: #cc9393;">"input"</span>, <span style="color: #cc9393;">"div"</span>,
            <span style="color: #cc9393;">"img"</span>, <span style="color: #cc9393;">"p"</span>, <span style="color: #cc9393;">"strong"</span>, <span style="color: #cc9393;">"br"</span>, <span style="color: #cc9393;">"ul"</span>,
            <span style="color: #cc9393;">"li"</span>, <span style="color: #cc9393;">"dd"</span>, <span style="color: #cc9393;">"dt"</span>, <span style="color: #cc9393;">"dl"</span>, <span style="color: #cc9393;">"a"</span>, <span style="color: #cc9393;">"meta"</span>,
            <span style="color: #cc9393;">"link"</span>, <span style="color: #cc9393;">"span"</span>, <span style="color: #cc9393;">"label"</span>, <span style="color: #cc9393;">"code"</span>,
            ] + [<span style="color: #cc9393;">"h%d"</span> % i <span style="color: #f0dfaf; font-weight: bold;">for</span> i <span style="color: #f0dfaf; font-weight: bold;">in</span><span style="color: #93e0e3;"> xrange</span>(<span style="color: #dcdccc; background-color: #3f3f3f;">6</span>)]:
    <span style="color: #dcdccc; background-color: #3f3f3f;">func</span> = t.substitute(tag=tag)
    <span style="color: #f0dfaf; font-weight: bold;">exec</span>(func)
</pre>


</div>

</div>

<div id="outline-container-4-3" class="outline-3">
<h3 id="sec-4-3"><span class="section-number-3">4.3</span> 选择头像</h3>
<div class="outline-text-3" id="text-4-3">

<p>   为了防止csrf，用户切换头像的请求都需要csrf_token。
</p>
</div>

</div>

<div id="outline-container-4-4" class="outline-3">
<h3 id="sec-4-4"><span class="section-number-3">4.4</span> 数据库设计</h3>
<div class="outline-text-3" id="text-4-4">

<p>   数据库的设计请见scheme.sql这个文件，主要分为用户表，图片表、头像表和session表。
</p>
<p>
   里面创建了数据库yagra以及用户yagra并为yagra授予必要的权限。
</p></div>
</div>
</div>
</div>

<div id="postamble">
<hr/><p class="author">Author: <a href="http://everet.org/about-me/">Hua Liang [Stupid ET]</a> </p>
<p class="date">Last Updated: 2013-03-14 19:28 Thursday</p>
</div>
</body>
</html>
